/*************************************************************************
 *                     COPYRIGHT NOTICE
 *            Copyright 2024 Horizon Robotics, Inc.
 *                   All rights reserved.
 *************************************************************************/
#include <fstream>
#include <sstream>
#include <iostream>
#include "gles_shader.hpp"

GLuint load_shader(const char* filename, GLenum shader_type)
{
	GLuint shader_id = 0;
	FILE* file;
	int32_t file_size = -1;
	char* glsl_source;
	GLint success;
	GLchar infoLog[1024];

	if (NULL != (file = fopen(filename, "rb")) &&
	    0 == fseek(file, 0, SEEK_END) &&
	    -1 != (file_size = ftell(file))) {
		rewind(file);

		if (NULL != (glsl_source = (char*)malloc(file_size + 1))) {
			if (file_size == (int32_t)fread(glsl_source, sizeof(char), file_size, file)) {
				glsl_source[file_size] = '\0';

				if (0 != (shader_id = glCreateShader(shader_type))) {
					glShaderSource(shader_id, 1, &glsl_source, NULL);
					glCompileShader(shader_id);
					glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success);
					if(!success) {
						shader_id = 0U;
						glGetShaderInfoLog(shader_id, 1024, NULL, infoLog);
						std::cout << "ERROR::SHADER_COMPILATION_ERROR: " << infoLog << std::endl;
					}
				} else {
					fprintf(stderr, "ERROR: Could not create a shader.\n");
				}
			} else {
				fprintf(stderr, "ERROR: Could not read file %s\n", filename);
			}

			free(glsl_source);
		} else {
			fprintf(stderr, "ERROR: Could not allocate %d bytes.\n", file_size);
		}

		fclose(file);
	} else {
		if (NULL != file) {
			fclose(file);
		}

		fprintf(stderr, "ERROR: Could not open file %s\n", filename);
	}

	return shader_id;
}

uint32_t create_program(const char* vertex_path, const char* fragment_path, const char* geometry_path,
			const char* tess_control_path, const char* tess_eval_path, const char* compute_path)
{
	uint32_t program_id;
	GLint linked = 0;
	GLchar infoLog[1024];
	
	struct shader_info {
		const char* shader_file_path;
		GLenum shader_type;
		int32_t shader_id = -1;
	} shader_infos[] = {
		{
			.shader_file_path = vertex_path,
			.shader_type = GL_VERTEX_SHADER
		},
		{
			.shader_file_path = fragment_path,
			.shader_type = GL_FRAGMENT_SHADER
		},
		{
			.shader_file_path = geometry_path,
			.shader_type = GL_GEOMETRY_SHADER
		},
		{
			.shader_file_path = tess_control_path,
			.shader_type = GL_TESS_CONTROL_SHADER
		},
		{
			.shader_file_path = tess_eval_path,
			.shader_type = GL_TESS_EVALUATION_SHADER
		},
		{
			.shader_file_path = compute_path,
			.shader_type = GL_COMPUTE_SHADER
		}
	};

	for (auto &shader_info : shader_infos) {
		if (shader_info.shader_file_path != nullptr) {
			shader_info.shader_id = load_shader(shader_info.shader_file_path, shader_info.shader_type);
		}
	}

	program_id = glCreateProgram();

	for (auto shader_info : shader_infos) {
		if (shader_info.shader_id > -1) {
			glAttachShader(program_id, shader_info.shader_id);
		}
	}

	glLinkProgram(program_id);

	glGetProgramiv(program_id, GL_LINK_STATUS, &linked);

	if (GL_FALSE == linked)
	{
		glGetProgramInfoLog(program_id, 1024, NULL, infoLog);
		std::cout << "Unable to link program - reason:" << infoLog << std::endl;
		return 0;
	}

	return program_id;
}